#include "GameEngine.hpp"

float FPS = 0;

using namespace std;

GLfloat checkDistance(GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2){
    return sqrt ( (x2 * x2 - 2 * x1 * x2 + x1 * x1) + (y2 * y2 - 2 * y1 * y2 + y1 * y1) );
}

/*_______________ colision handler class _______________*/

/*_______________ public function _______________*/
ColisionHandler::ColisionHandler(){}

void ColisionHandler::colision(GLfloat ax, GLfloat ay,
                               GLfloat bx, GLfloat by,
                               unsigned int position,
                               GLfloat *actingForceAtPosition,
                               GLfloat *worldPositions,
                               unsigned int *worldPositionsSize,
                               bool *drawPosiotion ){
                                    worldPositions[position*2] -= actingForceAtPosition[position*2];
                                    worldPositions[position*2+1] -= actingForceAtPosition[position*2+1];
                                    if(ax == bx) {
                                        actingForceAtPosition[position*2] *= -1;
                                   } else if(ay == by){
                                        actingForceAtPosition[position*2+1] *= -1;
                                   } else {
                                       cout << ax << " " << ay << " " << bx << " " << by << endl;
                                       cout << actingForceAtPosition[position*2] << " " << actingForceAtPosition[position*2+1] << endl;
                                       GLfloat A = -(by - ay);
                                       GLfloat B = bx - ax;

                                       GLfloat NA = B;
                                       GLfloat NB = -A;
                                       GLfloat NC = -(NA * actingForceAtPosition[position*2] + NB * actingForceAtPosition[position*2+1]);

                                       GLfloat WX = (-A*NC) / (A * NB - NA * B);
                                       GLfloat WY = (-B*NC) / (NA*B - A * NB);

                                       actingForceAtPosition[position*2] = 2*WX- actingForceAtPosition[position*2];
                                       actingForceAtPosition[position*2+1] = 2*WY - actingForceAtPosition[position*2+1];
                                       cout << actingForceAtPosition[position*2] << " " << actingForceAtPosition[position*2+1] << endl;
                                   }
                               }
/*________________________________________________________________________________________________________________________*/

/*_______________ level colision handler class _______________*/

/*_______________ public function _______________*/
void LevelColisionHandler::colision(GLfloat x1, GLfloat y1,
                               GLfloat x2, GLfloat y2,
                               unsigned int position,
                               GLfloat *actingForceAtPosition,
                               GLfloat *worldPositions,
                               unsigned int *worldPositionsSize,
                               bool *drawPosiotion ){
                                   drawPosiotion[position] = false;
                                }
/*________________________________________________________________________________________________________________________*/

/*_______________ paddle colision handler class _______________*/

/*_______________ public function _______________*/
void PaddleColisionHandler::colision(GLfloat x1, GLfloat y1,
                               GLfloat x2, GLfloat y2,
                               unsigned int position,
                               GLfloat *actingForceAtPosition,
                               GLfloat *worldPositions,
                               unsigned int *worldPositionsSize,
                               bool *drawPosiotion ){
                                    worldPositions[position*2] -= actingForceAtPosition[position*2];
                                    worldPositions[position*2+1] -= actingForceAtPosition[position*2+1];
                                }
/*________________________________________________________________________________________________________________________*/



/*_______________ matter class _______________*/

/*_______________ private function _______________*/
bool Matter::loadMatter(string matterPath){
    string line;
    ifstream matterFile(matterPath.c_str());

    if (matterFile.is_open()) {

        getline (matterFile, line);
        GLdraw = string(line);

        getline (matterFile, line);
        matterClass = atoi(line.c_str());

        while ( getline (matterFile, line) )
        {
            if(line.find("verticles") == 0){
                int startNum = line.find(" ") + 1;
                int endNum = line.find("\n");
                string num = line.substr(startNum, endNum);

                int i = atoi(num.c_str());
                gVertexBufferData = new GLfloat[i * 2];
                gVertexBufferDataSize = i;

                for(int j = 0; j < i; j++){
                    getline (matterFile, line);

                    startNum = 0;
                    endNum = line.find(" ");
                    GLfloat xy;
                    for(int k = 0; k < 2; k++){
                        if(endNum == -1) endNum = line.size();
                        num = line.substr(startNum, endNum);
                        xy = atof(num.c_str());

                        gVertexBufferData[j*2+k] = xy;

                        startNum = endNum;
                        endNum = line.find(" ", startNum+1);
                    }
                }
            }
            else if(line.find("colors") == 0){
                int startNum = line.find(" ") + 1;
                int endNum = line.find("\n");
                string num = line.substr(startNum, endNum);

                int i = atoi(num.c_str());
                gColorBufferData = new GLfloat*[i];
                gColorBufferDataSize = i;
                GLfloat *color;
                for(int j = 0; j < i; j++){
                    color = new GLfloat[gVertexBufferDataSize * 3];
                    for(unsigned int k = 0; k < gVertexBufferDataSize; k++){
                        getline (matterFile, line);

                        startNum = 0;
                        endNum = line.find(" ");

                        GLfloat xyz;
                        for(int l = 0; l < 3; l++){
                            if(endNum == -1) endNum = line.size();

                            num = line.substr(startNum, endNum);
                            xyz = atof(num.c_str());

                            color[k*3 + l] = xyz;

                            startNum = endNum;
                            endNum = line.find(" ", startNum+1);
                        }
                    }
                    gColorBufferData[j] = color;
                }
            }
            else if(line.find("pos&col") == 0){
                int startNum = line.find(" ") + 1;
                int endNum = line.find("\n");
                string num = line.substr(startNum, endNum);

                int i = atoi(num.c_str());

                worldPositions = new GLfloat[i * 2];
                worldPositionsSize = i;

                colorAtPosiotion = new unsigned int[i];

                matterAtPositionMoved = new bool[i];
                for(int k = 0; k < i; k++){
                    matterAtPositionMoved[k] = false;
                }

                drawPosiotion = new bool[i];
                for(int k = 0; k < i; k++){
                    drawPosiotion[k] = true;
                }

                actingForceAtPosition = new GLfloat[i * 2];
                for(int k = 0; k < i * 2; k++){
                    actingForceAtPosition[k] = 0;
                }

                for(int j = 0; j < i; j++){
                    getline (matterFile, line);

                    startNum = 0;
                    endNum = line.find(" ");
                    GLfloat xy;
                    for(int k = 0; k < 3; k++){
                        if(endNum == -1) endNum = line.size();
                        num = line.substr(startNum, endNum);

                        if(k == 2){
                            xy = atoi(num.c_str());
                            colorAtPosiotion[j] = xy;
                        }
                        else {
                            xy = atof(num.c_str());
                            worldPositions[j*2+k] = xy;
                        }

                        startNum = endNum;
                        endNum = line.find(" ", startNum+1);
                    }
                }
            } else if(line.find("map") == 0){
                int startNum = line.find(" ") + 1;
                int endNum = line.find("\n");
                string num = line.substr(startNum, endNum);

                int i = atoi(num.c_str());
                map = new GLfloat[i * 2];
                mapSize = i;

                for(int j = 0; j < i; j++){
                    getline (matterFile, line);

                    startNum = 0;
                    endNum = line.find(" ");
                    GLfloat xy;
                    for(int k = 0; k < 2; k++){
                        if(endNum == -1) endNum = line.size();
                        num = line.substr(startNum, endNum);
                        xy = atof(num.c_str());

                        map[j*2+k] = xy;

                        startNum = endNum;
                        endNum = line.find(" ", startNum+1);
                    }
                }
            }
        }
        matterFile.close();
    } else return false;

    return true;
}

/*_______________ public function _______________*/
Matter::Matter(string matterPath,  ColisionHandler *ch) : matterPath(matterPath), ch(ch){
    if(loadMatter(matterPath)){
        cout << "Load succesfoul: " << matterPath << endl;
    } else {
        cout << "Can't load: " << matterPath << endl;
    }
}

void Matter::moveMatternAtPosition(unsigned int position, float x, float y){
    if(position < worldPositionsSize) {
        unsigned int i = position*2;
        worldPositions[i] += x;
        worldPositions[i + 1] += y;
    } else {
        cout << "there is no matter at position: " << position << " in " << matterPath << endl;
    }
}

void Matter::setForceAtPosition(unsigned int position, GLfloat x, GLfloat y){
    if(position < worldPositionsSize) {
        unsigned int i = position*2;
        actingForceAtPosition[i] = x;
        actingForceAtPosition[i+1] = y;

        matterAtPositionMoved[position] = true;
    } else {
        cout << "there is no matter at position: " << position << " in " << matterPath << endl;
    }
}

void Matter::colision(GLfloat x1, GLfloat y1,
                      GLfloat x2, GLfloat y2,
                      unsigned int position){
    if(ch != NULL){
        ch->colision(x1, y1, x2, y2, position, actingForceAtPosition, worldPositions, &worldPositionsSize, drawPosiotion);
    } else cout << "no colision handler" << endl;

}

//getters
GLfloat *Matter::getVertexBufferData(){ return gVertexBufferData; }
unsigned int Matter::getVertexBufferDataSize(){ return gVertexBufferDataSize; }

GLfloat **Matter::getColorBufferData(){ return gColorBufferData; }
unsigned int Matter::getColorBufferDataSize(){ return gColorBufferDataSize; }
unsigned int Matter::getColorAtPosition(int position){ return colorAtPosiotion[position]; }

GLfloat *Matter::getWorldPositions(){ return worldPositions; }
unsigned int Matter::getWorldPositionsSize(){ return worldPositionsSize; }
bool *Matter::getDrawPosition(){ return drawPosiotion; }

bool *Matter::getMatterAtPositionMoved(){ return matterAtPositionMoved; }

GLfloat *Matter::getActingForceAtPosition(){ return actingForceAtPosition; }

GLfloat *Matter::getMap(){ return map; }
unsigned int Matter::getMapSize() { return mapSize; }

unsigned int Matter::getMatterClass(){ return matterClass; }

string Matter::getGLdraw(){ return GLdraw; }


/*________________________________________________________________________________________________________________________*/


/*_______________ key handler class _______________*/

/*_______________ private function _______________*/

/*_______________ public function _______________*/
KeyHandler::KeyHandler(int key, Matter *matter) : key(key), matter(matter){}
KeyHandler::KeyHandler(){}

void KeyHandler::keyPress(){
    if(glfwGetKey( key ) == GLFW_PRESS) {
        cout << "function keyPress() of key: " << key << ".  Not supported!" << endl;;
    }
}

MovePaddle::MovePaddle(int leftKey,
                       int rightKey,
                       int spaceKey,
                       Matter *paddle,
                       Matter *ball) :  leftKey(leftKey),
                                        rightKey(rightKey),
                                        spaceKey(spaceKey),
                                        paddle(paddle),
                                        ball(ball)
                                        {
                                            realaseBall = true;
                                            addForce = true;
                                        }

void MovePaddle::keyPress(){
    if(glfwGetKey( spaceKey ) == GLFW_PRESS && realaseBall){
        ball->setForceAtPosition(0, 0.0, 0.85 / (FPS*0.9) );
        realaseBall = false;
    } else if(glfwGetKey( leftKey ) == GLFW_PRESS && addForce){
            GLfloat speed = ( 0.85 / (FPS*0.5) );
            if(realaseBall){
                paddle->setForceAtPosition(0, -speed, 0.0);
                ball->setForceAtPosition(0, -speed, 0.0);
            } else {
                paddle->setForceAtPosition(0, -speed, 0.0);
            }
            addForce = false;
    } else if(glfwGetKey( rightKey ) == GLFW_PRESS && addForce){
            GLfloat speed = ( 0.85 / (FPS*0.5) );
            if(realaseBall){
                paddle->setForceAtPosition(0, speed, 0.0);
                ball->setForceAtPosition(0, speed, 0.0);
            } else {
                paddle->setForceAtPosition(0, speed, 0.0);
            }
            addForce = false;
    } else {
        paddle->setForceAtPosition(0, 0.0, 0.0);
        if(realaseBall) ball->setForceAtPosition(0, 0.0, 0.0);
        addForce = true;
    }
}

test::test(  int leftKey,
                   int rightKey,
                   int upKey,
                   int downKey,
                   Matter *ball) :  leftKey(leftKey),
                                    rightKey(rightKey),
                                    upKey(upKey),
                                    downKey(downKey),
                                    ball(ball){}

void test::keyPress(){
    GLfloat speed = ( 0.85 / (FPS*0.8) );
    if(glfwGetKey( leftKey ) == GLFW_PRESS){
            ball->setForceAtPosition(0, -speed, 0.0);
    } else if(glfwGetKey( rightKey ) == GLFW_PRESS){
            ball->setForceAtPosition(0, speed, 0.0);
    } else if(glfwGetKey( upKey ) == GLFW_PRESS){
            ball->setForceAtPosition(0, 0.0, speed);
    } else if(glfwGetKey( downKey ) == GLFW_PRESS){
            ball->setForceAtPosition(0, 0.0, -speed);
    }
}

/*________________________________________________________________________________________________________________________*/


/*_______________ game class _______________*/

/*_______________ private function _______________*/

/*_______________ public function _______________*/
Game::Game(){
    Matter *ball = new Matter("models/ball.m", new ColisionHandler());
    Matter *paddle = new Matter("models/Spaddle.m", new PaddleColisionHandler);

    matter = new Matter*[7];
    matter[0] = new Matter("models/backcground.m", NULL);
    matter[1] = new Matter("models/SLwall.m", NULL);
    matter[2] = new Matter("models/SRwall.m", NULL);
    matter[3] = new Matter("models/STwall.m", NULL);
    matter[4] = paddle;
    matter[5] = ball;
    matter[6] = new Matter("models/level1.m", new LevelColisionHandler());
    matterNumber = 7;

    keysHandlers = new KeyHandler*[1];

    keysHandlers[0] = new MovePaddle(GLFW_KEY_LEFT,
                               GLFW_KEY_RIGHT,
                               GLFW_KEY_SPACE,
                               paddle,
                               ball);
    keysHandlersNumber = 1;
}

void Game::reload(){

    for(unsigned int i = 0; i < matterNumber; i++){
        matter[i] = NULL;
        delete(matter[i]);
    }

    for(unsigned int i = 0; i < keysHandlersNumber; i++){
        keysHandlers[i] = NULL;
        delete(keysHandlers[i]);
    }

    Matter *ball = new Matter("models/ball.m", new ColisionHandler());
    Matter *paddle = new Matter("models/Spaddle.m", new PaddleColisionHandler);

    matter[0] = new Matter("models/backcground.m", NULL);
    matter[1] = new Matter("models/SLwall.m", NULL);
    matter[2] = new Matter("models/SRwall.m", NULL);
    matter[3] = new Matter("models/STwall.m", NULL);
    matter[4] = paddle;
    matter[5] = ball;
    matter[6] = new Matter("models/level1.m", new LevelColisionHandler());

    keysHandlers[0] = new MovePaddle(GLFW_KEY_LEFT,
                               GLFW_KEY_RIGHT,
                               GLFW_KEY_SPACE,
                               paddle,
                               ball);
}

//getters
Matter **Game::getMatter(){
    return matter;
}

unsigned int Game::getMatterNumber(){
    return matterNumber;
}

KeyHandler **Game::getKeysHandlers(){
    return keysHandlers;
}

unsigned int Game::getKeysHandlersNumber(){
    return keysHandlersNumber;
}

/*________________________________________________________________________________________________________________________*/


/*_______________ game engine class _______________*/

/*_______________ private function _______________*/

void GameEngine::calculateFPS(){
    //  Increase frame count
    frameCount++;

    //  Get the number of milliseconds since glutInit called
    //  (or first call to glutGet(GLUT ELAPSED TIME)).
    currentTime = glutGet(GLUT_ELAPSED_TIME);
    //  Calculate time passed
    long int timeInterval = currentTime - previousTime;
    if(timeInterval > 100)
    {
        //  calculate the number of frames per second
        fps[fpsCount] = frameCount / (timeInterval / 1000.f);

        if(fpsCount < fpsCountNum) fpsCount++;
        else fpsCount = 0;

        for(int i = 0; i < fpsCountNum; i++){
            fps[fpsCountNum] += fps[i];
        }
        fps[fpsCountNum] /= fpsCountNum + 1;

        //  Set time
        previousTime = currentTime;

        //  Reset frame count
        frameCount = 0;
    }

    FPS = fps[fpsCountNum];
}

/*_______________ public function _______________*/
GameEngine::GameEngine(){
    game = new Game();
    gpe = new GamePhysicEngine(game->getMatter(), game->getMatterNumber());
    gge = new GameGraphicEngine(game->getMatter(), game->getMatterNumber());

    fpsCount = 0;
    fpsCountNum = 10;
    fpsLimit = 60.0;
    sleepTime = 0;
    fps = new float[fpsCountNum + 1];
    for(int i = 0; i < fpsCount; i++){
        fps[i] = 0;
    }

    frameCount = 0;
    currentTime = 0;
    previousTime = glutGet(GLUT_ELAPSED_TIME);

    loop = true;
}

void GameEngine::startLoop(){
    KeyHandler **keysHandlers = game->getKeysHandlers();
    unsigned int keysHandlersNumber = game->getKeysHandlersNumber();

    bool reload = false;

    while(loop){
        calculateFPS();

        //keys handling
        for(unsigned int i = 0; i < keysHandlersNumber; i++){
            keysHandlers[i]->keyPress();
        }

        if(glfwGetKey( GLFW_KEY_BACKSPACE ) == GLFW_PRESS && !reload){
            game->reload();
            gge->reload();
            reload = true;
        } else if(glfwGetKey( GLFW_KEY_BACKSPACE ) != GLFW_PRESS && reload){
            reload = false;
        }

        if(glfwGetKey( GLFW_KEY_ESC ) == GLFW_PRESS){
            loop = false;
        }

        //graphic engine
        gge->graphicEngineLoop();

        //physic engine
        gpe->physicEngineLoop();
    }

    gge->destroyWindow();
}

/*________________________________________________________________________________________________________________________*/


/*_______________ game physic engine class _______________*/

/*_______________ private function _______________*/
void GamePhysicEngine::forceMoveMatter(){
    for(unsigned int i = 0; i < matterNumber; i++){
        bool *matterAtPositionMoved = matter[i]->getMatterAtPositionMoved();
        GLfloat *actingForceAtPosition = matter[i]->getActingForceAtPosition();

        for(unsigned int j = 0; j < matter[i]->getWorldPositionsSize(); j++){
            if(matterAtPositionMoved[j]){
                matter[i]->moveMatternAtPosition(j, actingForceAtPosition[j*2], actingForceAtPosition[j*2+1]);
            }
        }
    }
}

bool GamePhysicEngine::pointToLineColisionDetection(GLfloat ax, GLfloat ay,
                                  GLfloat bx, GLfloat by,
                                  GLfloat cx, GLfloat cy,
                                  GLfloat dx, GLfloat dy){

       GLfloat q = (ay - by) * (cx - dx) - (ax - bx) * (cy - dy);
       if(q == 0){
           return false;
       } else {
           GLfloat t = (ax*by - bx*ay + bx*cy - cx*by + cx*ay - ax*cy) / q;
           if(1 > t && t > 0){
               GLfloat s = (cx*dy - dx*cy + dx*ay - ax*dy + ax*cy - cx*ay) / q;
               if(1 > s && s > 0){
                   return true;
               }else return false;
           } else return false;
       }

       return false;
}

bool GamePhysicEngine::objectToObjectColisionDetection(Matter **matter, unsigned int matterNumber,
                                     unsigned int matI, unsigned int posJ,
                                     unsigned int matN, unsigned int posM,
                                     GLfloat *cx, GLfloat *cy,
                                     GLfloat *dx, GLfloat *dy){

    GLfloat *worldPositions = matter[matI]->getWorldPositions();
    GLfloat *getActingForceAtPosition = matter[matI]->getActingForceAtPosition();
    GLfloat *getMap = matter[matI]->getMap();
    unsigned int mapSize = matter[matI]->getMapSize();

    GLfloat *worldPositionsCheck = matter[matN]->getWorldPositions();
    bool *drawPosiotionCheck = matter[matN]->getDrawPosition();
    bool *matterAtPositionMovedCheck = matter[matN]->getMatterAtPositionMoved();
    GLfloat *getActingForceAtPositionCheck = matter[matN]->getActingForceAtPosition();
    GLfloat *getMapCheck = matter[matN]->getMap();
    unsigned int mapSizeCheck = matter[matN]->getMapSize();

    if( drawPosiotionCheck[posM] ){
        unsigned int tposJ = posJ*2;
        unsigned int tposM = posM*2;

        GLfloat ax;
        GLfloat ay;
        GLfloat bx;
        GLfloat by;
        if( matterAtPositionMovedCheck[posM] ){

            for(unsigned int y = 0; y < mapSize; y++){
            ax = getMap[y*2] + worldPositions[tposJ];
            ay = getMap[y*2+1] + worldPositions[tposJ+1];
            bx = ax + getActingForceAtPosition[tposJ];
            by = ay + getActingForceAtPosition[tposJ+1];

                for(unsigned int x = 1; x < mapSizeCheck; x++){
                    *cx = getMapCheck[(x-1)*2] + worldPositionsCheck[tposM] + getActingForceAtPositionCheck[tposM];
                    *cy = getMapCheck[(x-1)*2+1] + worldPositionsCheck[tposM+1] + getActingForceAtPositionCheck[tposM+1];

                    *dx = getMapCheck[x*2] + worldPositionsCheck[tposM] + getActingForceAtPositionCheck[tposM];
                    *dy = getMapCheck[x*2+1] + worldPositionsCheck[tposM+1] + getActingForceAtPositionCheck[tposM+1];

                    if(pointToLineColisionDetection(ax, ay,
                                                    bx, by,
                                                    *cx, *cy,
                                                    *dx, *dy)){
                                                        return true;
                                                    }
                }
            }
        } else {
            for(unsigned int y = 0; y < mapSize; y++){
            ax = getMap[y*2] + worldPositions[tposJ];
            ay = getMap[y*2+1] + worldPositions[tposJ+1];
            bx = ax + getActingForceAtPosition[tposJ];
            by = ay + getActingForceAtPosition[tposJ+1];

                for(unsigned int x = 1; x < mapSizeCheck; x++){
                    *cx = getMapCheck[(x-1)*2] + worldPositionsCheck[tposM];
                    *cy = getMapCheck[(x-1)*2+1] + worldPositionsCheck[tposM+1];

                    *dx = getMapCheck[x*2] + worldPositionsCheck[tposM];
                    *dy = getMapCheck[x*2+1] + worldPositionsCheck[tposM+1];

                    if(pointToLineColisionDetection(ax, ay,
                                                    bx, by,
                                                    *cx, *cy,
                                                    *dx, *dy)){
                                                        return true;
                                                    }
                }
            }
        }
    }

    return false;
}

void GamePhysicEngine::detectColision(){
    for(unsigned int i = 0; i < matterNumber; i++){
        unsigned int worldPositionsSize = matter[i]->getWorldPositionsSize();
        bool *drawPosiotion = matter[i]->getDrawPosition();
        bool *matterAtPositionMoved = matter[i]->getMatterAtPositionMoved();

        for(unsigned int j = 0; j < worldPositionsSize; j++){
            if(drawPosiotion[j] && matterAtPositionMoved[j]){
                for(unsigned int n = 0; n < matterNumber; n++) {
                    if(matter[i]->getMatterClass() <= matter[n]->getMatterClass()){
                        unsigned int worldPositionsSizeCheck = matter[n]->getWorldPositionsSize();
                        for(unsigned int m = 0; m < worldPositionsSizeCheck; m++){
                            if(i != n || j != m){
                                GLfloat cx, cy, dx, dy;
                                if(objectToObjectColisionDetection(matter, matterNumber, i, j, n, m, &cx, &cy, &dx, &dy)){
                                    matter[i]->colision(cx, cy, dx, dy, j);
                                    matter[n]->colision(0.0, 0.0, 0.0, 0.0, m);
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}


/*_______________ public function _______________*/

GamePhysicEngine::GamePhysicEngine(Matter **matter, unsigned int matterNumber): matter(matter), matterNumber(matterNumber){

}

void GamePhysicEngine::physicEngineLoop(){
    forceMoveMatter();
    detectColision();
}


/*________________________________________________________________________________________________________________________*/


/*_______________ game graphic engine class _______________*/

/*_______________ private function _______________*/
int GameGraphicEngine::initWinodw(){
    // Initialise GLFW
	if( !glfwInit() )
	{
		cout << "Failed to initialize GLFW\n" << endl;
		return 0;
	}

	glfwOpenWindowHint(GLFW_FSAA_SAMPLES, 4);
	glfwOpenWindowHint(GLFW_OPENGL_VERSION_MAJOR, 3);
	glfwOpenWindowHint(GLFW_OPENGL_VERSION_MINOR, 3);
	glfwOpenWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

	// Open a window and create its OpenGL context
	if( !glfwOpenWindow( 1024, 768, 0,0,0,0, 32,0, GLFW_WINDOW ) )
	{
		cout << "Failed to open GLFW window. If you have an Intel GPU, they are not 3.3 compatible. Try the 2.1 version of the tutorials.\n" << endl;
		glfwTerminate();
		return 0;
	}

	// Initialize GLEW
	glewExperimental = true; // Needed for core profile
	if (glewInit() != GLEW_OK) {
		cout << "Failed to initialize GLEW\n" << endl;
		return 0;
	}

	glfwSetWindowTitle( "Arkanoid" );

	// Ensure we can capture the escape key being pressed below
	glfwEnable( GLFW_STICKY_KEYS );

	// Dark blue background
	glClearColor(0.0f, 0.0f, 0.5f, 0.0f);

	glGenVertexArrays(1, &VertexArrayID);
	glBindVertexArray(VertexArrayID);

	return 1;
}

void GameGraphicEngine::compileShaders(){
    programID = LoadShaders( "SimpleVertexShader.vertexshader", "SimpleFragmentShader.fragmentshader" );
}

void GameGraphicEngine::loadMatterToGPU(){
    GPUVertexBuffers = new GLuint[matterNumber];
    GPUColorsBuffers = new GLuint*[matterNumber];

    for(unsigned int n = 0; n < matterNumber; n++){
        glGenBuffers(1, &GPUVertexBuffers[n]);
        glBindBuffer(GL_ARRAY_BUFFER, GPUVertexBuffers[n]);
        glBufferData(GL_ARRAY_BUFFER,
                     sizeof(GLfloat) * matter[n]->getVertexBufferDataSize() * 2,
                     matter[n]->getVertexBufferData(),
                     GL_STATIC_DRAW);

        GLfloat **gColorBufferData = matter[n]->getColorBufferData();
        GPUColorsBuffers[n] = new GLuint[ matter[n]->getColorBufferDataSize() ];
        for(unsigned int i = 0; i < matter[n]->getColorBufferDataSize(); i++){
            glGenBuffers(1, &GPUColorsBuffers[n][i]);
            glBindBuffer(GL_ARRAY_BUFFER, GPUColorsBuffers[n][i]);
            glBufferData(GL_ARRAY_BUFFER,
                         sizeof(GLfloat) * matter[n]->getVertexBufferDataSize() * 3,
                         gColorBufferData[i],
                         GL_STATIC_DRAW);
        }
    }
}

/*_______________ public function ___________________*/
GameGraphicEngine::GameGraphicEngine(Matter **matter, unsigned int matterNumber): matter(matter), matterNumber(matterNumber){
    GPUVertexBuffers = new GLuint[matterNumber];
    GPUColorsBuffers = new GLuint*[matterNumber];

    for(unsigned int n = 0; n < matterNumber; n++){
        GPUColorsBuffers[n] = new GLuint[ matter[n]->getColorBufferDataSize() ];
    }

    if(initWinodw()){
        compileShaders();
        loadMatterToGPU();
    } else {
        cout << "Error: Can't init winodw" << endl;
    }
}

void GameGraphicEngine::graphicEngineLoop(){
    // Clear the screen
    glClear( GL_COLOR_BUFFER_BIT );
    // Use our shader
    glUseProgram(programID);


    for(unsigned int n = 0; n < matterNumber; n++){
        // 1rst attribute buffer : vertices
        glEnableVertexAttribArray(0);
        glBindBuffer(GL_ARRAY_BUFFER, GPUVertexBuffers[n]);
        glVertexAttribPointer(
            0,                  // attribute 0. No particular reason for 0, but must match the layout in the shader.
            2,                  // size
            GL_FLOAT,           // type
            GL_FALSE,           // normalized?
            0,                  // stride
            (void*)0            // array buffer offset
        );

        GLfloat *wordPositions = matter[n]->getWorldPositions();
        bool *darawPosiotion = matter[n]->getDrawPosition();
        for(unsigned int i = 0; i < matter[n]->getWorldPositionsSize(); i++){
            if(darawPosiotion[i]){
                // 2nd attribute buffer : colors
                glEnableVertexAttribArray(1);
                glBindBuffer(GL_ARRAY_BUFFER, GPUColorsBuffers[n][ matter[n]->getColorAtPosition(i) ]);
                glVertexAttribPointer(
                    1,                                // attribute. No particular reason for 1, but must match the layout in the shader.
                    3,                                // size
                    GL_FLOAT,                         // type
                    GL_FALSE,                         // normalized?
                    0,                                // stride
                    (void*)0                          // array buffer offset
                );

                glUniform2f(0, wordPositions[i*2], wordPositions[i*2+1]);
                // Draw the triangle !
                string GLdraw = matter[n]->getGLdraw();

                if(!GLdraw.compare("GL_TRIANGLES")){
                    glDrawArrays(GL_TRIANGLES, 0, matter[n]->getVertexBufferDataSize());
                }
                else if(!GLdraw.compare("GL_LINE")){
                    glDrawArrays(GL_LINE, 0, matter[n]->getVertexBufferDataSize());
                }
                else if(!GLdraw.compare("GL_LINE_STRIP")){
                    glDrawArrays(GL_LINE_STRIP, 0, matter[n]->getVertexBufferDataSize());
                }
                else if(!GLdraw.compare("GL_TRIANGLE_FAN")){
                    glDrawArrays(GL_TRIANGLE_FAN, 0, matter[n]->getVertexBufferDataSize());
                }
            }
        }
    }
    glDisableVertexAttribArray(0);
    glDisableVertexAttribArray(1);

    // Swap buffers
    glfwSwapBuffers();
}

void GameGraphicEngine::destroyWindow(){
	// Cleanup VBO and shader

    for(unsigned int n = 0; n < matterNumber; n++){
            glDeleteBuffers(1, &GPUVertexBuffers[n]);
        for(unsigned int i = 0; i < matter[n]->getColorBufferDataSize(); i++){
            glDeleteBuffers(1, &GPUColorsBuffers[n][i]);
        }
    }

	glDeleteProgram(programID);
	glDeleteVertexArrays(1, &VertexArrayID);

	// Close OpenGL window and terminate GLFW
	glfwTerminate();
}

void GameGraphicEngine::reload(){
    for(unsigned int n = 0; n < matterNumber; n++){
            glDeleteBuffers(1, &GPUVertexBuffers[n]);
        for(unsigned int i = 0; i < matter[n]->getColorBufferDataSize(); i++){
            glDeleteBuffers(1, &GPUColorsBuffers[n][i]);
        }
    }

    loadMatterToGPU();
}
